
#include "RtBase.h"
#include "RtTimerQueueBase.h"
#include "RtReactorInterface.h"
#include "RtObserver.h"

CRtTimerQueueBase::CRtTimerQueueBase(IRtObserver *aObserver)
	: m_pObserver(aObserver)
{
}

CRtTimerQueueBase::~CRtTimerQueueBase()
{
}

int CRtTimerQueueBase::CheckExpire(CRtTimeValue *aRemainTime)
{
	int nCout = 0;
	CRtTimeValue tvCur = CRtTimeValue::GetTimeOfDay();

	/// Follow up linux kernel bug on gettimeofday() jumping into the future. 
	if (m_tick>tvCur)
	{
		RT_ERROR_TRACE_THIS("CheckExpire, time fall back! last="<<m_tick.GetTotalInMsec()
			<<" cur="<<tvCur.GetTotalInMsec()
			<<" diff="<<(m_tick.GetTotalInMsec()-tvCur.GetTotalInMsec()));
	}
	else if (tvCur.GetSec()>m_tick.GetSec()+3600 && m_tick.GetSec()!=0)
	{
		RT_INFO_TRACE_THIS("CheckExpire, big jump ahead! last="<<m_tick.GetSec()
			<<" cur="<<tvCur.GetSec()
			<<" diff="<<tvCur.GetSec()-m_tick.GetSec());
	}
	m_tick = tvCur;

	for ( ; ; ) {
		IRtTimerHandler *pEh = NULL;
		LPVOID pToken = NULL;
		{
			CRtTimeValue tvEarliest;
			CRtMutexGuardT<MutexType> theGuard(m_Mutex);
			int nRet = GetEarliestTime_l(tvEarliest);
			if (nRet == -1) {
				if (aRemainTime)
					*aRemainTime = CRtTimeValue::s_tvMax;
				break;
			}
			else if (tvEarliest > tvCur) {
				if (aRemainTime)
					*aRemainTime = tvEarliest - tvCur;
				break;
			}

			CNode ndFirst;
			nRet = PopFirstNode_l(ndFirst);
			RT_ASSERTE(nRet == 0);

			pEh = ndFirst.m_pEh;
			pToken = ndFirst.m_pToken;
			if (ndFirst.m_dwCount != (DWORD)-1)
				ndFirst.m_dwCount--;

			if (ndFirst.m_dwCount > 0 && ndFirst.m_tvInterval > CRtTimeValue::s_tvZero) {
				do {
					ndFirst.m_tvExpired += ndFirst.m_tvInterval;
				}
				while (ndFirst.m_tvExpired <= tvCur);

				RePushNode_l(ndFirst);
			}
		}

		RT_ASSERTE(pEh);
		pEh->OnTimeout(tvCur, pToken);
		nCout++;
	}

	if (aRemainTime && aRemainTime->GetSec()>3600)
	{
		RT_INFO_TRACE_THIS("CheckExpire, remain="<<aRemainTime->GetSec());
	}

	return nCout;
}

RtResult CRtTimerQueueBase::
ScheduleTimer(IRtTimerHandler *aEh, LPVOID aToken, 
			  const CRtTimeValue &aInterval, DWORD aCount)
{
	RT_ASSERTE_RETURN(aEh, RT_ERROR_INVALID_ARG);
	RT_ASSERTE_RETURN(aInterval > CRtTimeValue::s_tvZero || aCount == 1, RT_ERROR_INVALID_ARG);
	
	int nRet;
	BOOL bNeedNotify = FALSE;
	{
		CRtMutexGuardT<MutexType> theGuard(m_Mutex);
		CRtTimeValue tvTmp;
		if (m_pObserver)
			bNeedNotify = GetEarliestTime_l(tvTmp) == -1 ? TRUE : FALSE;
		
		CNode ndNew(aEh, aToken);
		ndNew.m_tvInterval = aInterval;
		ndNew.m_tvExpired = CRtTimeValue::GetTimeOfDay() + aInterval;
		if (aCount > 0)
			ndNew.m_dwCount = aCount;
		else
			ndNew.m_dwCount = (DWORD)-1;
		
		// PushNode_l() will check the same IRtTimerHandler in the queue 
		nRet = PushNode_l(ndNew);
	}

	if (bNeedNotify) {
		RT_ASSERTE(m_pObserver);
//		RT_INFO_TRACE("CRtTimerQueueBase::ScheduleTimer, time queue is empty, Notify it.");
		m_pObserver->OnObserve("TimerQueue");
	}

	// Timer queue is in the own thread, need not notify it.
	if (nRet == 0)
		return RT_OK;
	else if (nRet == 1)
		return RT_ERROR_FOUND;
	else
		return RT_ERROR_FAILURE;
}

RtResult CRtTimerQueueBase::CancelTimer(IRtTimerHandler *aEh)
{
	RT_ASSERTE_RETURN(aEh, RT_ERROR_INVALID_ARG);
	CRtMutexGuardT<MutexType> theGuard(m_Mutex);

	int nRet = EraseNode_l(aEh);
	if (nRet == 0)
		return RT_OK;
	else if (nRet == 1)
		return RT_ERROR_NOT_FOUND;
	else
		return RT_ERROR_FAILURE;
}

CRtTimeValue CRtTimerQueueBase::GetEarliestTime() 
{
	CRtTimeValue tvRet;
	CRtMutexGuardT<MutexType> theGuard(m_Mutex);

	int nRet = GetEarliestTime_l(tvRet);
	if (nRet == 0)
		return tvRet;
	else
		return CRtTimeValue::s_tvMax;
}

